﻿#region Using directives.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;
using System.Configuration;

#endregion
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// SearchProviderBuilder is a non-renderable component with design time support which builds and 
    /// registers <see cref="SearchProvider"/> instances and edits <see cref="ISearchClient"/> objects.
    /// </summary>
    [ToolboxData("<{0}:SearchProviderBuilder runat=server><{0}:SearchFilter runat=server /><{0}:SearchView runat=server /><{0}:CommandToolBar runat=server /></{0}:SearchProviderBuilder>")]
    [DesignerAttribute(typeof(ContainerControlDesigner))]
    [ParseChildren(false)]
    [Themeable(true)]
    public class SearchProviderBuilder : Control
    {
        #region DummyEngine.

        class DummyEngine : ISearchEngine
        {
            #region ISearchEngine.

            public void AddSortExpression(string field, bool ascending)
            {
            }

            public void ClearSortExpressions()
            {
            }

            public SearchEngineSortExpression[] GetSortExpressions()
            {
                return new SearchEngineSortExpression[0];
            }

            public System.Data.DataView FetchByPkValues(object[,] pkValues)
            {
                return new DataView();
            }

            public System.Data.DataView Search()
            {
                DataView view = new DataView();
                if (this.DataFetched != null)
                    DataFetched(this, new EventArgs<DataView>(view));

                if (this.DataReady != null)
                    DataReady(this, new EventArgs<DataView>(view));

                return new DataView();
            }

            public int Count()
            {
                return 0;
            }

            public IBindableFieldCollection DisplayFields
            {
                get { return new BindableFieldCollection(); }
            }

            public int MaxNumberOfRecordsReturned
            {
                get { return 0; }
                set { }
            }

            public PostProcessDelegate PostProcess
            {
                set { }
            }

            public IBindableFieldCollection PrimaryKeyFields
            {
                get
                {
                    BindableFieldCollection pkFields = new BindableFieldCollection();
                    pkFields.Add(null, null, typeof(object));
                    return pkFields;
                }
            }

            public IMethodRemoter RPC
            {
                get { return null; }
                set { }
            }

            public ISearchEngineFieldCollection SearchFields
            {
                get { return new DummySearchFieldCollection(); }
            }

            public bool SortOnDb
            {
                get { return false; }
                set { }
            }

            public event EventHandler<EventArgs<DataView>> DataFetched;

            public event EventHandler<EventArgs<DataView>> DataReady;

            #endregion
        }

        class DummySearchFieldCollection : ISearchEngineFieldCollection
        {
            #region ISearchEngineFieldCollection.

            public ISearchEngineField this[string bindingName]
            {
                get { return null; }
            }

            public ISearchEngineField this[int index]
            {
                get { return null; }
            }

            public int Count
            {
                get { return 0; }
            }

            public System.Collections.IEnumerator GetEnumerator()
            {
                return new ArrayList().GetEnumerator();
            }

            public void ResetAllFields()
            { }

            public void ResetAllFields(bool resetAutoPopulatedFields, bool resetHiddenFields)
            { }

            public string Name
            {
                get { return null; }
                set { }
            }

            public string Description
            {
                get { return null; }
                set { }
            }

            #endregion
        }

        #endregion

        #region FieldPermissions.

        string caption;
        bool filterDisplayed = true;
        bool quickFilterEnabled;
        string quickFilterCaption;

        string engineBuilderAssembly;
        string engineBuilderClass;

        string searchPageUrl;
        string searchPageProviderQueryKey;
        string searchPageGuidQueryKey;

        string searchClientAcceptCommandText;
        string searchclientCancelCommandText;

        #endregion

        #region Constructor.

        /// <summary>
        /// Initializes a new instance of the <b>SearchProviderBuilder</b> class.
        /// </summary>
        public SearchProviderBuilder()
        {
        }

        #endregion

        #region Filter and view components.

        /// <summary>
        /// Gets search filter component that will be used by the created <see cref="SearchProvider"/>.
        /// </summary>
        /// <returns><see cref="ISearchFilter"/> control or <b>null</b> if filter component is not defined.</returns>
        public virtual ISearchFilter GetFilterComponent()
        {
            foreach (Control ctl in this.Controls)
            {
                if (ctl is ISearchFilter)
                    return ctl as ISearchFilter;
            }

            return null;
        }

        /// <summary>
        /// Gets search view component that will be used by the created <see cref="SearchProvider"/>.
        /// </summary>
        /// <returns><see cref="ISearchView"/> control or <b>null</b> if filter component is not defined.</returns>
        public virtual ISearchView GetViewComponent()
        {
            foreach (Control ctl in this.Controls)
            {
                if (ctl is ISearchView)
                    return ctl as ISearchView;
            }

            return null;
        }

        IList<IWebCommand> viewCommands = new List<IWebCommand>();

        /// <summary>
        /// Gets a collection of <see cref="IWebCommand"/> objects that will be used by <see cref="ISearchView"/> for the created <see cref="SearchProvider"/>.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IList<IWebCommand> ViewCommands
        {
            get { return this.viewCommands; }
        }

        /// <summary>
        /// Gets a collection of <see cref="ICommandDisplay"/> components which display the commands provided
        /// by the <see cref="ISearchView"/> component in UI.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IList<ICommandDisplay> CommandDisplays
        {
            get
            {
                List<ICommandDisplay> displays = new List<ICommandDisplay>();
                foreach (Control ctl in this.Controls)
                {
                    if (ctl is ICommandDisplay)
                        displays.Add((ICommandDisplay)ctl);
                }

                return displays;
            }
        }

        #endregion

        #region EnsureEngineInstanceForChildComponents.

        /// <summary>
        /// Raises the <see cref="Control.Init"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            EnsureEngineInstanceForChildComponents();
            base.OnInit(e);
        }

        /// <summary>
        /// Raises the <see cref="Control.Load"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> object that contains the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            EnsureEngineInstanceForChildComponents();
            base.OnLoad(e);
        }

        /// <summary>
        /// Raises the <see cref="Control.PreRender"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            EnsureEngineInstanceForChildComponents();
            base.OnPreRender(e);

            // Sometimes UpdatePanel controls, even those which do not contain SearchProviderBuilder, reference 
            // Atlas-enabled search components (eg. CommandToolBar, CommandMenu). This may cause errors on the 
            // client because these components are not rendered on the client. SearchProviderBuilder is a 
            // designtime-only component and doesn't render it's content, ie. child controls, to client.
            // Atlas performs analysis in it's render stage. To prevent Atlas from referencing non-existing
            // controls on the client they are cleared in prerender stage on the server-side, too.
            bool isRunningInAtlasEnabledPage = (!DesignMode && ScriptManager.GetCurrent(this.Page) != null);
            if (isRunningInAtlasEnabledPage)
                this.Controls.Clear();
        }

        private void EnsureEngineInstanceForChildComponents()
        {
            ISearchFilter filter = GetFilterComponent();
            if (filter != null && filter.Engine == null)
                filter.Engine = new DummyEngine();

            ISearchView view = GetViewComponent();
            if (view != null && view.Engine == null)
                view.Engine = new DummyEngine();
        }

        #endregion

        #region SearchProvider.

        /// <summary>
        /// Creates a <see cref="SearchProvider"/> object.
        /// </summary>
        /// <returns>An instance of <see cref="SearchProvider"/> class.</returns>
        public virtual SearchProvider CreateSearchProvider()
        {
            SearchProvider provider = new SearchProvider();
            provider.Caption = this.Caption;
            provider.FilterDisplayed = this.FilterDisplayed;
            provider.QuickFilterEnabled = this.QuickFilterEnabled;
            provider.QuickFilterCaption = this.QuickFilterCaption;
            provider.EngineBuilderAssembly = this.EngineBuilder.GetType().Assembly.FullName;
            provider.EngineBuilderClass = this.EngineBuilder.GetType().FullName;
            provider.ExtendedProperties = new Dictionary<string, object>(this.ExtendedProperties);

            ISearchFilter filter = GetFilterComponent();
            if (filter != null)
            {
                provider.FilterAssembly = filter.GetType().Assembly.FullName;
                provider.FilterClass = filter.GetType().FullName;
                provider.FilterSettings = filter.GetSettings();
            }

            ISearchView view = GetViewComponent();
            if (view != null)
            {
                // Merge commands.
                foreach (IWebCommand editorCmd in this.ViewCommands)
                {
                    bool addCmdToView = true;
                    foreach (IWebCommand viewCmd in view.Commands)
                    {
                        if (editorCmd.Key == viewCmd.Key)
                            addCmdToView = false;
                    }

                    if (addCmdToView)
                        view.Commands.Add((IWebCommand)editorCmd.Clone());
                }

                provider.ViewAssembly = view.GetType().Assembly.FullName;
                provider.ViewClass = view.GetType().FullName;
                provider.ViewSettings = view.GetSettings();
            }

            foreach (ICommandDisplay display in this.CommandDisplays)
                provider.AddCommandDisplay(display);

            return provider;
        }

        /// <summary>
        /// Creates a unique search provider key for the given culture.
        /// </summary>
        /// <param name="culture">User UI culture. Nullable.</param>
        /// <remarks>Use the generated key to register provider in the provider registry.</remarks>
        public virtual string CreateSearchProviderKey(CultureInfo culture)
        {
            if (culture != null)
                return Page.GetType().FullName + "_" + this.ClientID + "_" + culture.Name;
            else
                return Page.GetType().FullName + "_" + this.ClientID;
        }

        /// <summary>
        /// Creates and registers a <see cref="SearchProvider"/> instance.
        /// </summary>
        /// <param name="culture">User UI culture for which the provider is registered. Nullable.</param>
        /// <remarks>The method will automatically create a unique search provider key for the given culture
        /// using default algorithm implemented by <see cref="CreateSearchProviderKey(CultureInfo)"/> method.</remarks>
        public virtual void RegisterSearchProvider(CultureInfo culture)
        {
            string providerKey = CreateSearchProviderKey(culture);
            RegisterSearchProvider(providerKey);
        }

        /// <summary>
        /// Creates and registers a <see cref="SearchProvider"/> instance.
        /// </summary>
        /// <param name="providerKey">Unique search provider key under which the provider is registered. 
        /// <b>null</b> is not allowed.</param>
        public virtual void RegisterSearchProvider(string providerKey)
        {
            if (providerKey == null)
                throw new ArgumentNullException("Null is not a valid search provider key.", "providerKey");

            SearchProvider provider = CreateSearchProvider();
            SearchProviderRegistry.GetInstance().RegisterProvider(providerKey, provider);
        }

        #endregion

        #region Search page.

        /// <summary>
        /// Gets or sets the URL of a page which uses/renders the created <see cref="SearchProvider"/>.
        /// </summary>
        /// <remarks>Set this property when using <see cref="ISearchPageClient"/> components which
        /// use a dedicated form or popup window to render a SearchProvider.</remarks>
        [Bindable(false)]
        [Category("Behavior")]
        [Description("URL of a page which uses/renders the created SearchProvider.")]
        [DefaultValue(null)]
        public virtual string SearchPage
        {
            get { return this.searchPageUrl; }
            set { this.searchPageUrl = value; }
        }

        /// <summary>
        /// Gets or sets a query string key by which a provider key is passed to the <see cref="SearchPage"/>.
        /// </summary>
        /// <remarks>Set this property when using <see cref="ISearchPageClient"/> components which
        /// use a dedicated form or popup window to render a SearchProvider. The key under which the
        /// provider is registered is passed in the query string.</remarks>
        [Bindable(false)]
        [Category("Behavior")]
        [Description("Query string key by which a provider key is passed to the SearchPage.")]
        [DefaultValue(null)]
        public virtual string SearchPageProviderQueryKey
        {
            get { return this.searchPageProviderQueryKey; }
            set { this.searchPageProviderQueryKey = value; }
        }

        /// <summary>
        /// Gets or sets a query string key which contains a GUID or a random number.
        /// </summary>
        /// <remarks>Usefull when you want to eliminate potential issues with browser cache.
        /// A GUID is appended to URL so that the browser cannot reuse a page stored in its cache.</remarks>
        [Bindable(false)]
        [Category("Behavior")]
        [Description("Query string key which contains a GUID or a random number.")]
        [DefaultValue(null)]
        public virtual string SearchPageGuidQueryKey
        {
            get { return this.searchPageGuidQueryKey; }
            set { this.searchPageGuidQueryKey = value; }
        }

        /// <summary>
        /// Creates a <see cref="SearchPage"/> URL for the created <see cref="SearchProvider"/>.
        /// </summary>
        /// <param name="culture">User UI culture for which the provider is registered. Nullable.</param>
        /// <returns>URL.</returns>
        /// <remarks>The method will automatically create a unique search provider key for the given culture
        /// using default algorithm implemented by <see cref="CreateSearchProviderKey(CultureInfo)"/> method.</remarks>
        public virtual string CreateSearchPageUrl(CultureInfo culture)
        {
            string providerKey = CreateSearchProviderKey(culture);
            return CreateSearchPageUrl(providerKey);
        }

        /// <summary>
        /// Creates a <see cref="SearchPage"/> URL for the created <see cref="SearchProvider"/>.
        /// </summary>
        /// <param name="providerKey">Unique search provider key under which the provider is registered. 
        /// <b>null</b> is not allowed.</param>
        public virtual string CreateSearchPageUrl(string providerKey)
        {
            if (providerKey == null)
                throw new ArgumentNullException("Null is not a valid search provider key.", "providerKey");

            if (string.IsNullOrEmpty(this.SearchPage) || string.IsNullOrEmpty(this.SearchPageProviderQueryKey))
                throw new InvalidOperationException("SearchPage and SearchPageProviderQueryKey properties must be set in order to create URL.");

            StringBuilder url = new StringBuilder();
            url.Append(this.SearchPage);
            url.Append("?");
            url.Append(this.SearchPageProviderQueryKey);
            url.Append("=");
            url.Append(providerKey);
            if (!string.IsNullOrEmpty(this.SearchPageGuidQueryKey))
            {
                url.Append("&");
                url.Append(this.searchPageGuidQueryKey);
                url.Append("=");
                url.Append(Guid.NewGuid().ToString());
            }

            return url.ToString();
        }

        #endregion

        #region SearchClient.

        /// <summary>
        /// Gets or sets the <b>Text</b> property of the <see cref="IWebCommand"/> which 
        /// accepts the selected values and sets them into the <see cref="ISearchClient"/> component.
        /// </summary>
        [Bindable(false)]
        [Category("Behavior")]
        [Description("Text of the IWebCommand which accepts the selected values and sets them into the ISearchClient component.")]
        [DefaultValue(null)]
        [Localizable(true)]
        public virtual string AcceptCommandText
        {
            get { return this.searchClientAcceptCommandText ?? UiMessages.OpenText; }
            set { this.searchClientAcceptCommandText = value; }
        }

        /// <summary>
        /// Gets or sets the <b>Text</b> property of the <see cref="IWebCommand"/> which cancels and closes the search provider/page.
        /// </summary>
        [Bindable(false)]
        [Category("Behavior")]
        [Description("Text of the IWebCommand which cancels and closes the search provider/page.")]
        [DefaultValue(null)]
        [Localizable(true)]
        public virtual string CancelCommandText
        {
            get { return this.searchclientCancelCommandText ?? UiMessages.CancelText; }
            set { this.searchclientCancelCommandText = value; }
        }

        /// <summary>
        /// Creates and registers a <see cref="SearchProvider"/> for use with the given <see cref="ISearchClient"/> component.
        /// </summary>
        /// <param name="culture">User UI culture for which the provider is registered. Nullable.</param>
        /// <param name="clientComponent">UI client component which uses the provider and accepts the values returned by the <see cref="ISearchView"/> component of the created provider.</param>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="acceptCommandParameters">Parameters (ie. data fields) required for the accept command.</param>
        /// <remarks><para>The method will automatically create a unique search provider key for the given culture
        /// using default algorithm implemented by <see cref="CreateSearchProviderKey(CultureInfo)"/> method.</para>
        /// <para><see cref="ISearchClient"/> component implements the logic which creates the <b>accept</b>
        /// and <b>cancel</b> command.</para></remarks>
        public virtual void RegisterForClient(CultureInfo culture, ISearchClient clientComponent, bool calledFromPopupWindow, string[] acceptCommandParameters)
        {
            string providerKey = CreateSearchProviderKey(culture);
            RegisterForClient(providerKey, clientComponent, calledFromPopupWindow, acceptCommandParameters);
        }

        /// <summary>
        /// Creates and registers a <see cref="SearchProvider"/> for use with the given <see cref="ISearchClient"/> component.
        /// </summary>
        /// <param name="providerKey">Unique search provider key under which the provider is registered. <b>null</b> is not allowed.</param>
        /// <param name="clientComponent">UI client component which uses the provider and accepts the values returned by the <see cref="ISearchView"/> component of the created provider.</param>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="acceptCommandParameters">Parameters (ie. data fields) required for the accept command.</param>
        /// <remarks><para><see cref="ISearchClient"/> component implements the logic which creates the <b>accept</b>
        /// and <b>cancel</b> command.</para></remarks>
        public virtual void RegisterForClient(string providerKey, ISearchClient clientComponent, bool calledFromPopupWindow, string[] acceptCommandParameters)
        {
            if (providerKey == null)
                throw new ArgumentNullException("Null is not a valid search provider key.", "providerKey");
            if (clientComponent == null)
                throw new ArgumentNullException("Client component may not be null.", "clientComponent");

            clientComponent.SearchProviderKey = providerKey;
            IWebCommand acceptCommand = clientComponent.CreateAcceptCommand(calledFromPopupWindow, this.AcceptCommandText, acceptCommandParameters);
            if (acceptCommand != null)
                this.ViewCommands.Insert(0, acceptCommand);

            IWebCommand cancelCommand = clientComponent.CreateCancelCommand(calledFromPopupWindow, this.CancelCommandText);
            if (cancelCommand != null)
                this.ViewCommands.Add(cancelCommand);

            RegisterSearchProvider(providerKey);
        }

        /// <summary>
        /// Creates and registers a <see cref="SearchProvider"/> for use with the given <see cref="ISearchPageClient"/> component.
        /// </summary>
        /// <param name="culture">User UI culture for which the provider is registered. Nullable.</param>
        /// <param name="clientComponent">UI client component which uses the provider and accepts the values returned by the <see cref="ISearchView"/> component of the created provider.</param>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="acceptCommandParameters">Parameters (ie. data fields) required for the accept command.</param>
        /// <remarks><para>The method will automatically create a unique search provider key for the given culture
        /// using default algorithm implemented by <see cref="CreateSearchProviderKey(CultureInfo)"/> method.</para>
        /// <para><see cref="ISearchClient"/> component implements the logic which creates the <b>accept</b>
        /// and <b>cancel</b> command.</para>
        /// <para><see cref="ISearchPageClient.SearchPageUrl"/> is set to value created by 
        /// <see cref="CreateSearchPageUrl(CultureInfo)"/> method.</para></remarks>
        public virtual void RegisterForClient(CultureInfo culture, ISearchPageClient clientComponent, bool calledFromPopupWindow, string[] acceptCommandParameters)
        {
            clientComponent.SearchPageUrl = CreateSearchPageUrl(culture);
            RegisterForClient(culture, (ISearchClient)clientComponent, calledFromPopupWindow, acceptCommandParameters);
        }

        /// <summary>
        /// Creates and registers a <see cref="SearchProvider"/> for use with the given <see cref="ISearchClient"/> component.
        /// </summary>
        /// <param name="providerKey">Unique search provider key under which the provider is registered. <b>null</b> is not allowed.</param>
        /// <param name="clientComponent">UI client component which uses the provider and accepts the values returned by the <see cref="ISearchView"/> component of the created provider.</param>
        /// <param name="calledFromPopupWindow">Indicates whether the provider is rendered in a popup/iframe window.</param>
        /// <param name="acceptCommandParameters">Parameters (ie. data fields) required for the accept command.</param>
        /// <remarks><para><see cref="ISearchClient"/> component implements the logic which creates the <b>accept</b>
        /// and <b>cancel</b> command.</para>
        /// <para><see cref="ISearchPageClient.SearchPageUrl"/> is set to value created by 
        /// <see cref="CreateSearchPageUrl(string)"/> method.</para></remarks>
        public virtual void RegisterForClient(string providerKey, ISearchPageClient clientComponent, bool calledFromPopupWindow, string[] acceptCommandParameters)
        {
            clientComponent.SearchPageUrl = CreateSearchPageUrl(providerKey);
            RegisterForClient(providerKey, (ISearchClient)clientComponent, calledFromPopupWindow, acceptCommandParameters);
        }

        #endregion

        /// <summary>
        /// Does nothing. SearchProviderBuilder is a design-time component which is not rendered to client.
        /// </summary>
        protected override void Render(HtmlTextWriter writer)
        {
        }

        #region Remove some inheritted properties from the property sheet.

        /// <summary>
        /// Gets or sets a value that indicates whether a server control is rendered as UI on the page. 
        /// </summary>
        /// <remarks>Not meaning. The component is never displayed on the client.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override bool Visible
        {
            get { return base.Visible; }
            set { base.Visible = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the server control persists its view state, 
        /// and the view state of any child controls it contains, to the requesting client. 
        /// </summary>
        /// <remarks>No meaning. The component is never displayed on the client.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override bool EnableViewState
        {
            get { return base.EnableViewState; }
            set { base.EnableViewState = value; }
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets the <see cref="SearchProvider.Caption"/> property of the created search provider.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [Description("Caption property of the created search provider.")]
        [DefaultValue("")]
        [Localizable(true)]
        public virtual string Caption
        {
            get { return this.caption ?? ""; }
            set { this.caption = value; }
        }

        /// <summary>
        /// Gets or sets whether the filter component should be initially displayed in UI.
        /// </summary>
        /// <value><b>true</b> if filter should be displayed; <b>false</b> otherwise. Default is <b>true</b>.</value>
        [Category("Appearance")]
        [Description("Indicates whether the filter component should be initially displayed in UI.")]
        [DefaultValue(true)]
        public virtual bool FilterDisplayed
        {
            get { return this.filterDisplayed; }
            set { this.filterDisplayed = value; }
        }

        /// <summary>
        /// Gets or sets whether quick filter commands will be automatically generated.
        /// </summary>
        /// <value><b>true</b> to generate quick filter; <b>false</b> otherwise. Default is <b>false</b>.</value>
        [Category("Behavior")]
        [Description("Indicates whether quick filter commands will be automatically generated.")]
        [DefaultValue(false)]
        public bool QuickFilterEnabled
        {
            get { return this.quickFilterEnabled; }
            set { this.quickFilterEnabled = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="SearchProvider.QuickFilterCaption"/> property of the created search provider.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [Description("Caption for quick filter section.")]
        [DefaultValue("")]
        [Localizable(true)]
        public string QuickFilterCaption
        {
            get { return this.quickFilterCaption; }
            set { this.quickFilterCaption = value; }
        }

        IDictionary<string, object> extendedProperties = new Dictionary<string, object>();

        /// <summary>
        /// Gets or sets the collection of customized user information mapped to <see cref="SearchProvider.ExtendedProperties"/> collection.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IDictionary<string, object> ExtendedProperties
        {
            get { return this.extendedProperties; }
            set { this.extendedProperties = value; }
        }

        #endregion

        #region EngineBuilder.

        ISearchEngineBuilder cachedEngineBuilderInstance;

        /// <summary>
        /// Gets or sets a <see cref="ISearchEngineBuilder"/> instance which will be used by the created search provider.
        /// </summary>
        /// <remarks><para>The setter also sets the <see cref="EngineBuilderAssembly"/> and
        /// <see cref="EngineBuilderClass"/> properties.</para>
        /// <para>The engine can be set by the <see cref="EngineBuilderAssembly"/> and
        /// <see cref="EngineBuilderClass"/> properties in which case it will be instantiated by reflection.</para></remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual ISearchEngineBuilder EngineBuilder
        {
            get
            {
                bool assembyAndClassNamesAreSet = (this.engineBuilderAssembly != null && this.engineBuilderClass != null);
                if (assembyAndClassNamesAreSet)
                {
                    if (this.cachedEngineBuilderInstance == null)
                    {
                        this.cachedEngineBuilderInstance = (ISearchEngineBuilder)Assembly.Load(this.engineBuilderAssembly).CreateInstance(this.engineBuilderClass);
                    }
                    else
                    {
                        string asmFullName = this.cachedEngineBuilderInstance.GetType().Assembly.FullName;
                        string asmShortName = asmFullName.Split(',')[0];
                        bool assemblyChanged = (this.engineBuilderAssembly != asmFullName && this.engineBuilderAssembly != asmShortName);
                        bool classChanged = (this.cachedEngineBuilderInstance.GetType().FullName != this.engineBuilderClass);
                        if (assemblyChanged || classChanged)
                            this.cachedEngineBuilderInstance = (ISearchEngineBuilder)Assembly.Load(this.engineBuilderAssembly).CreateInstance(this.engineBuilderClass);
                    }
                }

                return this.cachedEngineBuilderInstance;
            }
            set
            {
                try
                {
                    this.cachedEngineBuilderInstance = value;
                    this.engineBuilderAssembly = value.GetType().Assembly.FullName;
                    this.engineBuilderClass = value.GetType().FullName;
                }
                catch
                {
                    this.cachedEngineBuilderInstance = null;
                    this.engineBuilderAssembly = null;
                    this.engineBuilderClass = null;
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets or sets the <b>assemblyString</b> of the <see cref="Assembly"/> which contains the 
        /// <see cref="ISearchEngineBuilder"/> class used by the created search provider.
        /// </summary>
        /// <remarks>Setter automatically verifies that the specified assembly exists.</remarks>
        [Category("Behavior")]
        [Description("Assembly which contains the ISearchEngineBuilder class used by the created search provider.")]
        [DefaultValue(null)]
        public virtual string EngineBuilderAssembly
        {
            get { return this.engineBuilderAssembly; }
            set
            {
                // Verify that the assembly exists.
                if (value != null)
                {
                    Assembly asm;
                    try { asm = Assembly.Load(value); }
                    catch { asm = null; }

                    if (asm == null)
                    {
                        this.engineBuilderAssembly = null;
                        return;
                    }
                }

                this.engineBuilderAssembly = value;
            }
        }

        /// <summary>
        /// Gets or sets the <b>typeName</b> of the <see cref="ISearchEngineBuilder"/> class used by the created search provider.
        /// </summary>
        /// <remarks>Setter automatically verifies that the specified class exists in the assembly defined
        /// by the <see cref="EngineBuilderAssembly"/> property.</remarks>
        [Category("Behavior")]
        [Description("ISearchEngineBuilder class used by the created search provider.")]
        [DefaultValue(null)]
        public virtual string EngineBuilderClass
        {
            get { return this.engineBuilderClass; }
            set
            {
                // Class can be verified only if assembly can be loaded.
                if (value != null)
                {
                    Assembly asm;
                    try { asm = (this.engineBuilderAssembly != null) ? Assembly.Load(this.engineBuilderAssembly) : null; }
                    catch { asm = null; }

                    if (asm != null)
                    {
                        ISearchEngineBuilder instance;
                        try { instance = (ISearchEngineBuilder)asm.CreateInstance(value); }
                        catch { instance = null; }

                        if (instance == null)
                        {
                            this.engineBuilderClass = null;
                            return;
                        }
                    }
                }

                this.engineBuilderClass = value;
            }
        }

        #endregion
    }
}